﻿/*
 *  Author: Louis A. Leskoviansky
 *  Company: Artemis Solutions Group (http://www.artemis-solutions.com)
 */


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Web;
using System.Web.UI;

namespace AjaxControlFramework
{
    public class EmbeddedScriptReference : ScriptReference
    {
        //------// Properties \\--------------------------------------------\\
        private string _qualifiedName = String.Empty;
        public virtual string QualifiedName
        {
            get { return _qualifiedName; }
            set
            {
                if (value == null)
                {
                    _qualifiedName = String.Empty;
                }
                else
                {
                    _qualifiedName = value;
                }
            }
        }


        private string _assembly = String.Empty;
        public virtual string Assembly
        {
            get { return _assembly; }
            set
            {
                if (value == null)
                {
                    _assembly = String.Empty;
                }
                else
                {
                    _assembly = value;
                }
            }
        }
        //------\\ Properties //--------------------------------------------//



        //------// Constructors \\------------------------------------------\\
        public EmbeddedScriptReference(string name, string qualifiedName, string assembly)
            : base(name)
        {
            QualifiedName = qualifiedName;
            Assembly = assembly;
        }
        //------\\ Constructors //------------------------------------------//



        //------// Methods \\-----------------------------------------------\\
        public override string GenerateScriptBlock()
        {
            string resourceUrl = String.Empty;

            Page currentPage = HttpContext.Current.CurrentHandler as Page;
            if (currentPage != null)
            {
                resourceUrl = currentPage.ClientScript.GetWebResourceUrl(GenerateAssemblyType(), QualifiedName);
            }

            StringBuilder scriptBlock = new StringBuilder(50 + resourceUrl.Length);
            scriptBlock.Append("<script type=\"text/javascript\" src=\"").Append(resourceUrl).Append("\"></script>");

            return scriptBlock.ToString();
        }


        protected virtual Type GenerateAssemblyType()
        {
            Assembly scriptAssembly = null;
            if (Assembly == null || Assembly == String.Empty)
            {
                scriptAssembly = System.Reflection.Assembly.GetExecutingAssembly();
            }
            else
            {
                scriptAssembly = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name.ToLower() == (Assembly ?? String.Empty).ToLower()).FirstOrDefault();
            }

            // Validate the referenced assembly to ensure it exists and that the specified script exists.
            ValidateScriptResource(scriptAssembly);


            // Attempt to load a single Type from the specified assembly from the "Items" cache.
            Type resourceType = null;
            if (HttpContext.Current.Items[scriptAssembly.FullName + "_LoadingType"] == null)
            {
                // If a loading type cannot be found, attempt to get a reference to a type that is currently 
                // loaded into the assembly referenced by the "Assembly" property.
                Type[] assemblyTypes = null;

                try
                {
                    assemblyTypes = scriptAssembly.GetTypes();
                }
                catch (System.Reflection.ReflectionTypeLoadException exception)
                {
                    assemblyTypes = exception.Types;
                }


                if (assemblyTypes == null || assemblyTypes.Length == 0)
                {
                    // If a loading type can't be extracted from the referenced assembly, create one dynamically (this is last resort).
                    resourceType = AppDomain.CurrentDomain.DefineDynamicAssembly(scriptAssembly.GetName(), System.Reflection.Emit.AssemblyBuilderAccess.ReflectionOnly).DefineDynamicModule("_" + Assembly).DefineType("temptype").CreateType(); // I'm sorry.
                }
                else
                {
                    resourceType = assemblyTypes[0];
                }

                // Store the Type reference in the "Items" cache so that other EmbedScript controls on the page that may 
                // reference the same assembly won't have to find a type reference.
                HttpContext.Current.Items[scriptAssembly.FullName + "_LoadingType"] = resourceType;
            }
            else
            {
                resourceType = (Type)HttpContext.Current.Items[scriptAssembly.FullName + "_LoadingType"];
            }

            return resourceType;
        }


        protected virtual void ValidateScriptResource(Assembly scriptAssembly)
        {
            if (scriptAssembly == null)
            {
                throw new HttpException("The " + Assembly + " assembly referenced by the " + base.Name + " script reference is not currently loaded.");
            }
            else
            {
                string scriptResource = scriptAssembly.GetManifestResourceNames().Where(r => r.EndsWith(QualifiedName)).FirstOrDefault();
                if (scriptResource == null)
                {
                    throw new HttpException("The " + QualifiedName + " embedded script resource referenced by the " + base.Name + " script reference could not be found.");
                }
                else
                {
                    object[] attributes = scriptAssembly.GetCustomAttributes(typeof(WebResourceAttribute), false);
                    if (attributes == null || attributes.Length == 0 || attributes.Where(a => (a as WebResourceAttribute).WebResource == QualifiedName).FirstOrDefault() == null)
                    {
                        throw new HttpException("The " + QualifiedName + " embedded script resource referenced by the " + base.Name + " script reference does not have a WebResourceAttribute attribute defined for it within the referenced assembly.");
                    }
                }
            }
        }
        //------\\ Methods //-----------------------------------------------//
    }
}
